﻿namespace Microsoft.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection Replace<TService, TImplementation>(this IServiceCollection services)
        where TImplementation : TService
    {
        return services.Replace<TService>(typeof(TImplementation));
    }

    public static IServiceCollection Replace<TService>(this IServiceCollection services, Type implementationType)
    {
        return services.Replace(typeof(TService), implementationType);
    }

    public static IServiceCollection Replace(this IServiceCollection services, Type serviceType, Type implementationType)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        if (serviceType == null)
        {
            throw new ArgumentNullException(nameof(serviceType));
        }

        if (implementationType == null)
        {
            throw new ArgumentNullException(nameof(implementationType));
        }

        if (!services.TryGetDescriptors(serviceType, out var descriptors))
        {
            throw new ArgumentException($"No services found for {serviceType.FullName}.", nameof(serviceType));
        }

        foreach (var descriptor in descriptors)
        {
            var index = services.IndexOf(descriptor);

            services.Insert(index, descriptor.WithImplementationType(implementationType));

            services.Remove(descriptor);
        }

        return services;
    }

    private static bool TryGetDescriptors(this IServiceCollection services, Type serviceType, out ICollection<ServiceDescriptor> descriptors)
    {
        return (descriptors = services.Where(service => service.ServiceType == serviceType).ToArray()).Any();
    }

    private static ServiceDescriptor WithImplementationType(this ServiceDescriptor descriptor, Type implementationType)
    {
        return new ServiceDescriptor(descriptor.ServiceType, implementationType, descriptor.Lifetime);
    }
}
